Skip to content

[DX12] Add support for Samplers in DX12.#1331

Open
manon-traverse wants to merge 4 commits into
llvm:mainfrom
Traverse-Research:dx12-sampler-support
Open

[DX12] Add support for Samplers in DX12.#1331
manon-traverse wants to merge 4 commits into
llvm:mainfrom
Traverse-Research:dx12-sampler-support

Conversation

@manon-traverse

@manon-traverse manon-traverse commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Adds support for samplers in DX12.
This change is a lot larger than you would expect from adding samplers.
This is because samplers need to live in a separate DescriptorHeap which cascaded into a couple of issues:

  • Building the descriptor tables requires building tables for both sampler and CBV/SRV/UAV descriptors.
  • The binding of descriptor tables can no longer be implied based on the DescriptorSets defined in .test file
  • A layout must be generated when the root signature is generated based on the bindings
  • In case of root signatures defined in the shader, the layout must be extracted from that root signature by deserializing it.

Adding this feature does allow more tests to succeed on DX12.

Comment thread lib/API/DX/Device.cpp Outdated
Comment thread lib/API/DX/Device.cpp
Comment on lines +324 to +341
enum class RootParameterType : uint32_t {
DescriptorTable = 0,
SamplerTable,
Constant,
CBV,
SRV,
UAV,
};

struct RoogtSignatureLayout {
RootParameterType ParameterType : 3;
uint32_t Count : 29;

RoogtSignatureLayout(RootParameterType ParameterType, uint32_t Count)
: ParameterType(ParameterType), Count(Count) {}
RoogtSignatureLayout() = delete;
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could've probably been two PRs, but at least this way it doesn't only introduces new API but it also shows how it's used

Comment thread include/API/Texture.h
Comment on lines -108 to -113
// Depth formats require DepthStencil usage; non-depth formats forbid it.
if (IsDepth && !IsDS)
return llvm::createStringError(
std::errc::invalid_argument,
"Depth format '%s' requires DepthStencil usage.",
getFormatName(Desc.Fmt).data());

@MarijnS95 MarijnS95 Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope / unrelated?

Comment thread lib/API/DX/Device.cpp
Ranges.get()[RangeIdx].OffsetInDescriptorsFromTableStart =
SamplerDescriptorIdx;

assert(Binding.DescriptorCount == 1 && "Manon expected this to be 1.");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug text?

Comment thread lib/API/DX/Device.cpp
UAV,
};

struct RoogtSignatureLayout {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am groot (roogt?)

Suggested change
struct RoogtSignatureLayout {
struct RootSignatureLayout {

@MarijnS95 MarijnS95 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more, on top of the inline notes:

  • Texture.h — dropping the "depth format requires DepthStencil usage" check is unrelated to samplers, and the depth-as-SRV path it enables still isn't correct (typed D32_FLOAT can't back an SRV without TYPELESS; those tests stay XFAIL). Maybe split out.
  • Implicit fallback now hard-errors on shader-embedded inline descriptors / root constants when no explicit RootParameters are given (previously bound each set as a table). No test hits it, but it's a behavior change.
  • assert(... "Manon expected this to be 1.") silently caps sampler arrays at 1 — prefer a real createStringError stating the invariant.
  • RoogtSignatureLayout (and the error string RootParamters) is misspelled.

Cleared on inspection: SamplerCreateDesc brace-init order matches the struct, the filter/comparison/address enum mappings are correct, and the 256-cap SamplerAllocator matches the existing allocator pattern.

Comment thread include/API/Sampler.h
@@ -0,0 +1,67 @@
//===- Sampler.h - Offload API Texture ------------------------------------===//

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//===- Sampler.h - Offload API Texture ------------------------------------===//
//===- Sampler.h - Offload API Sampler ------------------------------------===//

Comment thread include/API/Sampler.h
Comment on lines +8 to +10
//
//
//===----------------------------------------------------------------------===//

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//
//
//===----------------------------------------------------------------------===//

Comment thread include/API/Sampler.h
Comment on lines +54 to +56
// Sampler(Sampler &&) = delete;
Sampler &operator=(const Sampler &) = delete;
// Sampler &operator=(Sampler &&) = delete;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Sampler(Sampler &&) = delete;
Sampler &operator=(const Sampler &) = delete;
// Sampler &operator=(Sampler &&) = delete;
Sampler &operator=(const Sampler &) = delete;

Comment thread lib/API/DX/Device.cpp
const ComPtr<ID3D12DescriptorHeap> &SamplerHeap) {
// Bind descriptors in descriptor tables.
if (!DescHeap)
if (!DescHeap && !SamplerHeap)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!DescHeap && !SamplerHeap)
if (!DescHeap || !SamplerHeap)

Because both are unconditionally dereferenced afterwards.

Comment thread lib/API/DX/Device.cpp
for (uint32_t Idx = 0u; Idx < P.Sets.size(); ++Idx) {
DXCB.CmdList->SetComputeRootDescriptorTable(Idx, Handle);
Handle.Offset(P.Sets[Idx].Resources.size(), Inc);
for (uint32_t I = 0, N = DXPipeline.Layout.size(); I < N; ++I) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude thinks this loop can be deduplicated leaving only the graphics vs compute-specific calls on the API.

Comment thread lib/API/DX/Device.cpp
Comment on lines 2755 to 2759
case dx::RootParamKind::DescriptorTable:
// TODO(manon): Add support for descriptor tables containing samplers
DXCB.CmdList->SetComputeRootDescriptorTable(RootParamIndex++, Handle);
Handle.Offset(P.Sets[DescriptorTableIndex++].Resources.size(), Inc);
break;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Samplers live in a separate heap, so a DescriptorTable mixing a sampler with CBV/SRV/UAV resources binds the wrong heap and Handle.Offset() over-advances past the descriptors actually written (no SamplerTable case here either). Hard-error until it's supported (case needs braces for the local):

Suggested change
case dx::RootParamKind::DescriptorTable:
// TODO(manon): Add support for descriptor tables containing samplers
DXCB.CmdList->SetComputeRootDescriptorTable(RootParamIndex++, Handle);
Handle.Offset(P.Sets[DescriptorTableIndex++].Resources.size(), Inc);
break;
case dx::RootParamKind::DescriptorTable: {
const DescriptorSet &Set = P.Sets[DescriptorTableIndex++];
// TODO(manon): Add support for descriptor tables containing samplers.
for (const auto &R : Set.Resources)
if (R.isSampler())
return llvm::createStringError(
"Descriptor tables containing samplers are not yet "
"supported with explicit RootParameters.");
DXCB.CmdList->SetComputeRootDescriptorTable(RootParamIndex++, Handle);
Handle.Offset(Set.Resources.size(), Inc);
break;
}

Comment thread lib/API/DX/Device.cpp
break;
case DescriptorKind::SAMPLER:
llvm_unreachable("Not implemented yet."); // Requires a separate heap
llvm_unreachable("Sampler should have been filtered out.");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think this is dead code because of the continue on line 1425

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, which is why I put the unreachable statement there. I want all cases to be handled instead of relying on the default case label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants